Skip to content

Backup: wire the modernized dashboard to real REST + Gates#48889

Draft
dhasilva wants to merge 10 commits into
trunkfrom
update/backup-modernization-data-layer
Draft

Backup: wire the modernized dashboard to real REST + Gates#48889
dhasilva wants to merge 10 commits into
trunkfrom
update/backup-modernization-data-layer

Conversation

@dhasilva

Copy link
Copy Markdown
Contributor

Fixes # n/a

Proposed changes

  • Wire the modernized VaultPress Backup dashboard (PR Backup: modernized dashboard UI — Overview, Restore, Download (mocked) #48853, behind rsm_jetpack_ui_modernization_backup) to real REST endpoints. The mocked-data scaffold from Phase 1 is replaced with TanStack Query 5 hooks talking to a thin set of PHP REST bridges (Capabilities, Activity_Log, File_Browser, Download, Restore) under projects/packages/backup/src/rest/. The legacy dashboard remains byte-identical when the modernization filter is off.
  • <Gates> mounts inside <DashboardLayout> between the QueryClientProvider and the body: top-to-bottom loading → not-connected → secondary-admin → no-plan → children. Connection state is read from window.JP_CONNECTION_INITIAL_STATE, emitted inline by Jetpack_Backup::render_connection_initial_state on admin_print_scripts priority 1 — the modernized enqueue path short-circuits the legacy Connection_Initial_State::render_script() call, so without this <Gates> would sit on its loading skeleton forever. We avoid pulling in @automattic/jetpack-connection because its barrel imports SCSS wp-build can't resolve cleanly.
  • Activity list (Overview) drives DataViews from useActivityLog. WPCOM's /sites/{id}/activity/rewindable endpoint doesn't accept page or search, so the hook fetches a 100-item window and DataViews owns pagination + filter + search client-side. The hook returns both totalItems and totalPages (the trunk ActivityList passes both into DataViews' paginationInfo). getCachedActivityById reads the same window for the right-pane lookup so we don't re-fetch the selected row.
  • File browser uses useFileTree. WPCOM's /rewind/backup/ls actually returns contents as a map keyed by filename, not an array. The real folder/leaf discriminator is has_children, not type — inside /wp-content, folders like languages and mu-plugins are reported as type: 'file' with has_children: true. The virtual type: 'wordpress' core-version markers are filtered out.
  • File preview uses useFileContents. VaultPress addresses file blobs by the per-entry period — the timestamp when that file last changed — not the parent backup's rewindId. The bridge query param is named file_period accordingly, with a long docblock capturing the failure mode if it's reverted (silently signs a URL for a non-existent snapshot, then 400 "File not found" on stream). FileNodeFile carries period and the volume-prefixed manifestPath (e.g. f5:/wp-config.php) so the card has everything it needs without a separate metadata call.
  • Capabilities_Bridge proxies WPCOM's /sites/{id}/rewind/capabilities — the dedicated capabilities endpoint, same one the legacy plugin already uses. The previously-considered /sites/{id}/rewind?force=wpcom returned a state-shaped response whose capabilities key was missing on multi-product plans (Jetpack Complete), producing a false "no plan" gate.
  • Restore + Download screens use useRestore / useDownload (TanStack useMutation to kick off + polled useQuery for status) with the same idle → submitting → progress → success | error state machine the Phase 1 mocks defined. The Restore bridge signs against rest/v1 /activity-log/{site}/rewind/to/{rewindId} as_user — blog-token auth is rejected by WPCOM with That API call is not allowed for this account; comment in the bridge captures the original investigation. Both screens derive their displayed timestamp directly from the rewindId (Unix-seconds), so they don't depend on Overview having populated its cache first.
  • No more file metadata fetch: WPCOM's /sites/{id}/rewind/backup/path-info returns "No file found" for every input variant we synthesized and has no working caller anywhere in the monorepo. lastModified comes from /ls's period and mime type is inferred from the file extension instead; the unused endpoint + hook + tests are dropped.
  • Stats display: item.stats now reads from entry.content.text ("44 plugins, 23 themes, 1562 uploads, 99 posts, 18 pages") instead of entry.object.backup_stats (a stringified JSON blob in production that was getting rendered verbatim).
  • Drop the ?jpb-mock=1 developer affordance and the dev-mode banner now that everything is real. Removes use-is-mock-mode, the four use-mock-* hooks, the dev-mode-banner component, the three fixture files, and the <DevModeBanner /> mount in DashboardLayout.
  • Tests: Jest covers toIntRewindId (3 cases) + normalize/activity-log.ts (6 cases); phpunit smoke-tests each bridge — confirms routes only register behind the modernization filter and reject subscriber-level access with 403.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  1. Check out this branch, run pnpm install from the repo root, then composer install in projects/packages/backup/. Build the package with pnpm run build inside projects/packages/backup/.
  2. Enable the modernization filter via mu-plugin:
    <?php
    add_filter( 'rsm_jetpack_ui_modernization_backup', '__return_true' );
  3. Visit wp-admin → Jetpack → VaultPress Backup.
  4. On a real backup-plan'd site: confirm the activity list populates from WPCOM, clicking a backup row loads the file browser, expanding a folder issues a real /jetpack/v4/rewind/backup/ls call, clicking a text file (e.g. readme.html) opens the FileInfoCard with Modified (from /ls period), Type (inferred from the extension), and a monospace preview body (≤ 64 KB).
    • Note: some files may show "Preview unavailable for this file." even when their extension is text-shaped. This happens when the file's period predates available content blobs (VaultPress retains manifest entries longer than blob storage) — it's expected, not a regression.
  5. On a site WITHOUT a backup plan: confirm <Gates> renders the "no plan" fallback with a CTA to jetpack.com/upgrade/backup/. Tested on a fully-connected Jetpack Complete site too — should pass through to the body since Complete includes Backup.
  6. On a disconnected site: confirm <Gates> renders the "Connect Jetpack" fallback.
  7. Click "Download backup" → submit → progress → success surfaces the signed download URL. Click "Restore to this point" → submit → progress polls every 1.5 s → success or error notice.
  8. Toggle the modernization filter OFF (rename the mu-plugin to .off, reload). Confirm the legacy Backup admin renders unchanged.

🤖 Generated with Claude Code

dhasilva and others added 8 commits May 16, 2026 16:51
Adds the @tanstack/react-query infrastructure with no behavior change.
DashboardLayout now wraps children in a QueryClientProvider so every
screen in every route shares a module-scope singleton client — caches
survive navigation between routes.

Phase 2 setup commit. Capability gates and real REST hooks follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the per-feature REST bridges pattern with a thin
Rest_Controller that registers routes only when the modernization
filter is on. Capabilities_Bridge proxies /sites/{id}/rewind to
expose hasBackupPlan + planSlug for the React layer.

Gates mounts inside DashboardLayout between the QueryClientProvider
and the body slot. Top-to-bottom: loading → not-connected →
secondary-admin → no-plan → children. All three routes inherit
gating for free.

Connection state comes from window.JP_CONNECTION_INITIAL_STATE,
emitted inline on the modernized Backup admin page from
class-jetpack-backup.php (the modernized enqueue path skips the
legacy render_script call, so we need to put the global up ourselves).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces useMockActivityLog with useActivityLog backed by an
apiFetch -> bridge -> WPCOM /sites/{id}/activity/rewindable chain.
A small normalize/activity-log.ts maps WPCOM's rewindable-activity
shape (gridicon, content.text, object.backup_stats) onto the
ActivityItem discriminated union the UI already consumes.

The hook returns `totalItems` in addition to `totalPages` because the
trunk ActivityList drives DataViews' `paginationInfo` from both. The
WPCOM endpoint doesn't accept `page` or `search`, so the hook fetches
a 100-item window and filters/paginates client-side — DataViews
remains the single source of truth for the visible slice.

Overview's right-pane lookup swaps from the fixture's
findActivityById to getCachedActivityById, which reads the same
single-window cache the list populates. The default selection
(newest backup) is derived from the cache the same way.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the File_Browser_Bridge (ls + path-info + file-content), each
proxying WPCOM /sites/{id}/rewind/backup/* via as_user. The file-content
endpoint resolves the signed stream URL server-side and caps the
returned body at 64 KB so previews can't balloon the REST response.

React side: useFileTree replaces useMockFileTree, useFileContents
replaces the static findContents() fixture lookup. The rewindId prop
on FileBrowser is now actually consumed and threaded through every
NodeRow so each folder fetches its own children.

FileInfoCard no longer reads metadata from fixture nodes — usePathInfo
fetches size + mime + last-modified for the open file, and
useFileContents pulls the preview body only when the mime type is
text-shaped. FileNodeFile's metadata fields drop to optional since
WPCOM's /ls endpoint only returns name + type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useDownload replaces useMockDownload, driving the same
idle → submitting → progress → success | error state machine off
a TanStack useMutation (initiate) + polled useQuery (status).
The bridge proxies WPCOM's /rewind/backups/{rewindId}/prepare-download
and /rewind/backups/{rewindId}/downloads/{id} endpoints.

The screen now derives its "download point" timestamp directly from
the rewindId (it's unix-seconds) instead of resolving it through the
activity-log fixture — Download / Restore can stand alone without
relying on Overview having populated its cache first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useRestore replaces useMockRestore, driving the same state machine off
a TanStack useMutation (initiate) + polled useQuery (status). The
bridge proxies WPCOM's v1 /activity-log/{site}/rewind/to/{rewindId}
and /rewind/{restoreId}/restore-status endpoints, with the verbatim
comment explaining why the rewind endpoint must sign as_user
(blog-token auth is rejected by WPCOM with "That API call is not
allowed for this account").

Same rewindId → unix-seconds derivation as the Download screen, so
Restore stands alone without depending on Overview's cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes the last mock-mode artifacts: useIsMockMode, the dev banner
component, the four mock hooks, the three fixture files, and the
<DevModeBanner /> mount inside DashboardLayout. The modernized
Backup dashboard now only renders real data via the jetpack/v4/*
bridges, with <Gates> handling the not-connected and no-plan
fallbacks.

This completes Phase 2 of the Backup modernization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One test class per bridge:
- Rest_Capabilities_Bridge_Test (2 tests, 2 assertions)
- Rest_Activity_Log_Bridge_Test (2 tests, 2 assertions)
- Rest_File_Browser_Bridge_Test (4 tests, 4 assertions)
- Rest_Download_Bridge_Test (3 tests, 4 assertions)
- Rest_Restore_Bridge_Test (3 tests, 4 assertions)

Each class confirms the routes only register when the modernization
filter is on and that the manage_options permission_check rejects
subscriber-level access with a 403. Upstream WPCOM calls aren't
exercised here — they need network and a real connection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack or WordPress.com Site Helper), and enable the update/backup-modernization-data-layer branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack update/backup-modernization-data-layer
bin/jetpack-downloader test jetpack-mu-wpcom-plugin update/backup-modernization-data-layer

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added [Package] Backup [Plugin] Backup A plugin that allows users to save every change and get back online quickly with one-click restores. [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Tests] Includes Tests labels May 16, 2026
@github-actions

github-actions Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

The Jetpack plugin has different release cadences depending on the platform:

  • WordPress.com Simple releases happen as soon as you deploy your changes after merging this PR (PCYsg-Jjm-p2).
  • WoA releases happen weekly.
  • Releases to self-hosted sites happen monthly:
    • Scheduled release: June 2, 2026
    • Code freeze: June 1, 2026

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Backup plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@jp-launch-control

jp-launch-control Bot commented May 16, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 6 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/backup/src/dashboard/components/file-info-card/index.tsx 0/18 (0.00%) 0.00% 10 💔
projects/packages/backup/src/class-jetpack-backup.php 53/415 (12.77%) -0.16% 5 💔
projects/packages/backup/src/dashboard/screens/download.tsx 0/10 (0.00%) 0.00% 4 💔
projects/packages/backup/src/dashboard/screens/restore.tsx 0/10 (0.00%) 0.00% 4 💔
projects/packages/backup/src/dashboard/components/file-browser/index.tsx 0/151 (0.00%) 0.00% 1 ❤️‍🩹

27 files are newly checked for coverage. Only the first 5 are listed here.

File Coverage
projects/packages/backup/src/dashboard/components/gates/index.tsx 0/11 (0.00%) 💔
projects/packages/backup/src/dashboard/components/gates/no-backup-plan.tsx 0/2 (0.00%) 💔
projects/packages/backup/src/dashboard/components/gates/not-connected.tsx 0/2 (0.00%) 💔
projects/packages/backup/src/dashboard/components/gates/secondary-admin.tsx 0/2 (0.00%) 💔
projects/packages/backup/src/dashboard/data/api/activity-log.ts 0/1 (0.00%) 💔

Full summary · PHP report · JS report

If appropriate, add one of these labels to override the failing coverage check: Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code Coverage tests to be added later Use to ignore the Code coverage requirement check when tests will be added in a follow-up PR I don't care about code coverage for this PR Use this label to ignore the check for insufficient code coveage.

dhasilva and others added 2 commits May 17, 2026 01:00
REST bridges:
- Centralize the not-connected check via Rest_Controller::get_blog_id_or_error
  so every bridge short-circuits with a 412 instead of building /sites/0/…
- Require a user-level WPCOM connection in Rest_Controller::permission_check,
  matching the jetpack-activity-log package's pattern
- Rename Activity_Log_Bridge's route from /jetpack/v4/activity-log →
  /jetpack/v4/site/rewindable-activity to avoid colliding with the
  jetpack-activity-log package, which owns that route inside the Jetpack plugin
- Cap File_Browser_Bridge's preview proxy at the HTTP transport layer
  (limit_response_size) so multi-GB blobs can't be buffered into PHP memory
  before truncation, and validate the signed URL with wp_http_validate_url
- Drop the raw upstream WPCOM body from Restore_Bridge's WP_Error envelope —
  the React layer only reads err.message

React layer:
- Convert getCachedActivityById / getCachedDefaultBackupRewindId to
  useQuery-backed hooks so Overview's default selection reconciles to the
  newest backup the moment the activity-log window resolves
- Collapse the activityLog cache key to a single shared activityLogWindow
  key the hook always uses
- Drop the raw error_code fallback in useRestore — never surface
  machine identifiers like "checksum_mismatch" to users
- Refresh stale doc comments (DashboardLayout no longer mentions the
  dev-mode banner; FileBrowser references useFileTree instead of
  useMockFileTree)

Changelog:
- Align the Jetpack plugin entry with Phase 1: Type: other for the
  flag-gated rollout

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…es + review cleanup

The RestoreItemsChecklist selection was collected into local state on the
Restore and Download screens but never sent to the bridge, so an unchecked
"Plugins" or "Themes" was silently restored anyway. Thread `RestoreItems`
through `useRestore` / `useDownload` → `initiateRestore` / `initiateDownload`
as the mutation variable, and surface it to the bridges as `data: { types }`
(the keys already match WPCOM's `types` payload).

Also clears the review suggestions from the same pass:

* `useFileContents` browser path now UTF-8 encodes via TextEncoder before
  btoa, so non-ASCII manifest paths (accented or CJK upload filenames)
  no longer throw InvalidCharacterError.
* `useConnection` drops its empty-deps useMemo and reads
  JP_CONNECTION_INITIAL_STATE on every render — single property lookup,
  removes any risk of capturing a pre-emit empty snapshot if load order
  ever shifts.
* `query-client.ts` docstring corrected: each wp-build route is its own
  bundle so the cache is per-route, not shared across navigation. The
  design doesn't depend on cross-route reuse, but the comment shouldn't
  claim otherwise.
* `Restore_Bridge::initiate_restore` returns the same user-facing
  "Could not start the backup restore." string in both failure branches
  instead of the diagnostic "Restore response missing restore id."
* `Activity_Log_Bridge` drops the declared-but-unused `aggregate`,
  `after`, `before` args (and mirrors the cleanup in
  `data/api/activity-log.ts`'s FetchArgs) — the hook only ever passes
  `number`.
* Download success link gains `download` + `rel="noreferrer"` so the
  signed URL triggers a download instead of navigating away when WPCOM
  omits Content-Disposition.

PHP + JS tests pass; phan unchanged (only the pre-existing build/ artifact
warning); eslint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Backup [Plugin] Backup A plugin that allows users to save every change and get back online quickly with one-click restores. [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] In Progress [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant